/* Definitions for IO routines (unix version).

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cerrno>
#include <cstdlib>

KP_DECLS_BEGIN

fhandle_t llio_open (interpreter *interp, const char *path,
                     const char *mode, int *flagp)
{
  int sflags, nflags;

  switch (*mode++)
    {
      case 'r':
        nflags = O_RDONLY;
        sflags = STRM_READ;
        break;
      case 'w':
        nflags = O_WRONLY | O_CREAT | O_TRUNC;
        sflags = STRM_WRITE;
        break;
      case 'a':
        nflags = O_WRONLY | O_CREAT | O_APPEND;
        sflags = STRM_WRITE | STRM_APP;
        break;
      default:
        return (-1);
    }

  switch (*mode++)
    {
      case '+':
        nflags = (nflags & ~(O_WRONLY | O_RDONLY)) | O_RDWR;
        sflags |= STRM_READ | STRM_WRITE;
        break;
      case 'b':
        sflags |= STRM_BIN;
      case '\0':
        --mode;
        break;
      default:
        return (-1);
    }

  if (*mode == 'b')
    {
      if (sflags & STRM_BIN)
        return (-1);
      
      sflags |= STRM_BIN, ++mode;
    }

  if (*mode == 'c')
    {
      if (!(sflags & STRM_RDWR) || (nflags & O_CREAT))
        return (-1);

      nflags |= O_CREAT, ++mode;
    }
  else if (*mode == 'x')
    {
      if (!(nflags & O_TRUNC))
        return (-1);
      nflags |= O_EXCL, ++mode;
    }

  if (*mode != '\0')
    return (-1);

  if (flagp)
    *flagp = sflags;

  while (true)
    {
      int ret = open (path, nflags, S_IRUSR | S_IWUSR |
                      S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
      if (ret >= 0 || errno != EINTR)
        return (ret);
    }
}

bool llio_close (fhandle_t fd)
{
  return (close (fd) == 0);
}

result<bool> llio_fhstat (interpreter *interp, fhandle_t fd, fhandle_stat& out)
{
  struct stat st;
  KP_VTRY (interp->begin_blocking ());
  int rv = fstat (fd, &st);
  interp->end_blocking ();

  if (rv < 0)
    return (false);

  out.atime = st.st_atime;
  out.mtime = st.st_mtime;
  out.ctime = st.st_ctime;
  out.size = st.st_size;
  out.nlink = st.st_nlink;
  out.dev = st.st_dev;
  out.ino = st.st_ino;
  out.mode = st.st_mode;
  out.uid = st.st_uid;
  out.gid = st.st_gid;
  out.rdev = st.st_rdev;
  out.blksize = st.st_blksize;
  out.blocks = st.st_blocks;

  return (true);
}

bool llio_truncate (fhandle_t fd, uint64_t nsize)
{
  return (ftruncate (fd, nsize) == 0);
}

result<int64_t> llio_readb (interpreter *interp, fhandle_t fd,
                            char *dst, uint64_t nb)
{
  while (true)
    {
      KP_VTRY (interp->begin_blocking ());
      int64_t ret = read (fd, dst, nb);
      interp->end_blocking ();

      if (kp_unlikely (ret < 0 && errno == EINTR))
        KP_VTRY (interp->handle_evs ());
      else
        return (ret);
    }
}

result<int64_t> llio_writeb (interpreter *interp, fhandle_t fd,
                             const char *src, uint64_t nb)
{
  while (true)
    {
      KP_VTRY (interp->begin_blocking ());
      int64_t ret = write (fd, src, nb);
      interp->end_blocking ();

      if (kp_unlikely (ret < 0 && errno == EINTR))
        KP_VTRY (interp->handle_evs ());
      else
        return (ret);
    }
}

result<bool> llio_seek (interpreter *, fhandle_t fd, int64_t& off, int whence)
{
  if (sizeof (off_t) < sizeof (int64_t) &&
      (off > INT32_MAX || off < INT32_MIN))
    return (false);

  errno = 0;
  int64_t ret = lseek (fd, off, whence);
  if (errno)
    return (false);

  off = ret;
  return (true);
}

result<object> llio_fhpath (interpreter *interp, const char *path)
{
  // XXX: Implement this by hand because it's racy on many systems (i.e: musl)
  char *sp = realpath (path, nullptr);
  if (!sp)
    kp_return (UNBOUND);

  auto ret = string::make (interp, sp);
  free (sp);
  return (ret);
}

static int
do_init_llio (interpreter *)
{
  return (init_op::result_ok);
}

KP_DECLS_END

